﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Drawing.Win32;
using System.Drawing;
using System.Text;
using Layers;

namespace HoldemHook
{

    public interface ITextOutEventSource
    {
        event EventHandler<TextOutEventArgs> TextOutEvent;
    }


    /// <summary>
    /// Provides details about a Window returned by the 
    /// enumeration
    /// </summary>
    [Serializable]
    public class WinWnd : ITextOutEventSource
    {
        #region Structures
        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }
        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        private struct FLASHWINFO
        {
            public int cbSize;
            public IntPtr hwnd;
            public int dwFlags;
            public int uCount;
            public int dwTimeout;
        }
        #endregion

        #region UnManagedMethods
        private class UnManagedMethods
        {
            [DllImport("user32")]
            public extern static int IsWindowVisible(
                IntPtr hWnd);
            [DllImport("user32", CharSet = CharSet.Auto)]
            public extern static int GetWindowText(
                IntPtr hWnd,
                StringBuilder lpString,
                int cch);
            [DllImport("user32", CharSet = CharSet.Auto)]
            public extern static int GetWindowTextLength(
                IntPtr hWnd);
            [DllImport("user32")]
            public extern static int BringWindowToTop(IntPtr hWnd);
            [DllImport("user32")]
            public extern static int SetForegroundWindow(IntPtr hWnd);
            [DllImport("user32")]
            public extern static int IsIconic(IntPtr hWnd);
            [DllImport("user32")]
            public extern static int IsZoomed(IntPtr hwnd);
            [DllImport("user32", CharSet = CharSet.Auto)]
            public extern static int GetClassName(
                IntPtr hWnd,
                StringBuilder lpClassName,
                int nMaxCount);
            [DllImport("user32")]
            public extern static int FlashWindow(
                IntPtr hWnd,
                ref FLASHWINFO pwfi);
            [DllImport("user32")]
            public extern static int GetWindowRect(
                IntPtr hWnd,
                ref RECT lpRect);
            [DllImport("user32", CharSet = CharSet.Auto)]
            public extern static int SendMessage(
                IntPtr hWnd,
                int wMsg,
                IntPtr wParam,
                IntPtr lParam);
            [DllImport("user32", CharSet = CharSet.Auto)]
            public extern static uint GetWindowLong(
                IntPtr hwnd,
                int nIndex);

            [DllImport("user32", CharSet = CharSet.Auto)]
            public extern static IntPtr GetParent(IntPtr hwnd);

            public const int WM_COMMAND = 0x111;
            public const int WM_SYSCOMMAND = 0x112;

            public const int SC_RESTORE = 0xF120;
            public const int SC_CLOSE = 0xF060;
            public const int SC_MAXIMIZE = 0xF030;
            public const int SC_MINIMIZE = 0xF020;

            public const int GWL_STYLE = (-16);
            public const int GWL_EXSTYLE = (-20);

            /// <summary>
            /// Stop flashing. The system restores the window to its original state.
            /// </summary>
            public const int FLASHW_STOP = 0;
            /// <summary>
            /// Flash the window caption. 
            /// </summary>
            public const int FLASHW_CAPTION = 0x00000001;
            /// <summary>
            /// Flash the taskbar button.
            /// </summary>
            public const int FLASHW_TRAY = 0x00000002;
            /// <summary>
            /// Flash both the window caption and taskbar button.
            /// </summary>
            public const int FLASHW_ALL = (FLASHW_CAPTION | FLASHW_TRAY);
            /// <summary>
            /// Flash continuously, until the FLASHW_STOP flag is set.
            /// </summary>
            public const int FLASHW_TIMER = 0x00000004;
            /// <summary>
            /// Flash continuously until the window comes to the foreground. 
            /// </summary>
            public const int FLASHW_TIMERNOFG = 0x0000000C;
        }
        #endregion

        /// <summary>
        /// The window handle.
        /// </summary>
        private IntPtr hWnd = IntPtr.Zero;
        private IntPtr hParentWnd
        {
            get { return UnManagedMethods.GetParent(hWnd); }
        }

        public WinWnd Parent
        {
            get { return WinWndMap.Instance[hParentWnd]; }
        }

        private EventHandler<TextOutEventArgs> _textOutEvent;

        public event EventHandler<TextOutEventArgs> TextOutEvent
        {
            add { _textOutEvent += value; }
            remove { _textOutEvent -= value; }
        }

        protected virtual void OnTextOut(float X, float Y, string msg, CallEventArgs e)
        {
            if (null != _textOutEvent)
                _textOutEvent(this, new TextOutEventArgs(this, new PointF(X, Y), msg, e.InTime));
        }
        /// <summary>
        /// Called when real hWnd window was linked to this WinWnd object
        /// </summary>
        /// <param name="other">null: THIS window was initially linked to real
        /// not null: some other window was linked</param>
        public virtual void OnWindowLinked(WinWnd other)
        {
        }

        public void CopyFrom(WinWnd other)
        {
            this.hWnd = other.hWnd;
        }

        public static T ParentOfType<T>(WinWnd w) where T : WinWnd
        {
            IntPtr hp = w.hParentWnd;
            while (hp != IntPtr.Zero)
            {
                w = WinWndMap.Instance[hp];
                if (null != w && w is T)
                    return w as T;
                hp = UnManagedMethods.GetParent(hp);
            }
            return null;
        }

        /// <summary>
        /// To allow items to be compared, the hash code
        /// is set to the Window handle, so two EnumWindowsItem
        /// objects for the same Window will be equal.
        /// </summary>
        /// <returns>The Window Handle for this window</returns>
        public override System.Int32 GetHashCode()
        {
            return (System.Int32)this.hWnd;
        }

        /// <summary>
        /// Gets the window's handle
        /// </summary>
        public IntPtr Handle
        {
            get
            {
                return this.hWnd;
            }
        }

        public IntPtr ParentHandle
        {
            get
            {
                return UnManagedMethods.GetParent(this.hWnd);
            }
        }

        /// <summary>
        /// Gets the window's title (caption)
        /// </summary>
        public string Text
        {
            get
            {
                StringBuilder title = new StringBuilder(260, 260);
                UnManagedMethods.GetWindowText(this.hWnd, title, title.Capacity);
                return title.ToString();
            }
        }

        /// <summary>
        /// Gets the window's class name.
        /// </summary>
        public string ClassName
        {
            get
            {
                StringBuilder className = new StringBuilder(260, 260);
                UnManagedMethods.GetClassName(this.hWnd, className, className.Capacity);
                return className.ToString();
            }
        }

        /// <summary>
        /// Gets/Sets whether the window is iconic (mimimised) or not.
        /// </summary>
        public bool Iconic
        {
            get
            {
                return ((UnManagedMethods.IsIconic(this.hWnd) == 0) ? false : true);
            }
            set
            {
                UnManagedMethods.SendMessage(
                    this.hWnd,
                    UnManagedMethods.WM_SYSCOMMAND,
                    (IntPtr)UnManagedMethods.SC_MINIMIZE,
                    IntPtr.Zero);
            }
        }

        /// <summary>
        /// Gets/Sets whether the window is maximised or not.
        /// </summary>
        public bool Maximised
        {
            get
            {
                return ((UnManagedMethods.IsZoomed(this.hWnd) == 0) ? false : true);
            }
            set
            {
                UnManagedMethods.SendMessage(
                    this.hWnd,
                    UnManagedMethods.WM_SYSCOMMAND,
                    (IntPtr)UnManagedMethods.SC_MAXIMIZE,
                    IntPtr.Zero);
            }
        }

        /// <summary>
        /// Gets whether the window is visible.
        /// </summary>
        public bool Visible
        {
            get
            {
                return ((UnManagedMethods.IsWindowVisible(this.hWnd) == 0) ? false : true);
            }
        }

        /// <summary>
        /// Gets the bounding rectangle of the window
        /// </summary>
        public System.Drawing.Rectangle Rect
        {
            get
            {
                RECT rc = new RECT();
                UnManagedMethods.GetWindowRect(
                    this.hWnd,
                    ref rc);
                System.Drawing.Rectangle rcRet = new System.Drawing.Rectangle(
                    rc.Left, rc.Top,
                    rc.Right - rc.Left, rc.Bottom - rc.Top);
                return rcRet;
            }
        }

        /// <summary>
        /// Gets the location of the window relative to the screen.
        /// </summary>
        public System.Drawing.Point Location
        {
            get
            {
                System.Drawing.Rectangle rc = Rect;
                System.Drawing.Point pt = new System.Drawing.Point(
                    rc.Left,
                    rc.Top);
                return pt;
            }
        }

        /// <summary>
        /// Gets the size of the window.
        /// </summary>
        public System.Drawing.Size Size
        {
            get
            {
                System.Drawing.Rectangle rc = Rect;
                System.Drawing.Size sz = new System.Drawing.Size(
                    rc.Right - rc.Left,
                    rc.Bottom - rc.Top);
                return sz;
            }
        }

        /// <summary>
        /// Restores and Brings the window to the front, 
        /// assuming it is a visible application window.
        /// </summary>
        public void Restore()
        {
            if (Iconic)
            {
                UnManagedMethods.SendMessage(
                    this.hWnd,
                    UnManagedMethods.WM_SYSCOMMAND,
                    (IntPtr)UnManagedMethods.SC_RESTORE,
                    IntPtr.Zero);
            }
            UnManagedMethods.BringWindowToTop(this.hWnd);
            UnManagedMethods.SetForegroundWindow(this.hWnd);
        }

        public Api.WindowStyleFlags WindowStyle
        {
            get
            {
                return (Api.WindowStyleFlags)UnManagedMethods.GetWindowLong(
                    this.hWnd, UnManagedMethods.GWL_STYLE);
            }
        }

        public Api.ExtendedWindowStyleFlags ExtendedWindowStyle
        {
            get
            {
                return (Api.ExtendedWindowStyleFlags)UnManagedMethods.GetWindowLong(
                    this.hWnd, UnManagedMethods.GWL_EXSTYLE);
            }
        }

        public EnumWindowsCollection Children
        {
            get
            {
                EnumWindows enuw = new EnumWindows();
                enuw.GetWindows(hWnd);
                return enuw.Items;
            }
        }
        /// <summary>
        ///  Constructs a new instance of this class for
        ///  the specified Window Handle.
        /// </summary>
        /// <param name="hWnd">The Window Handle</param>
        public WinWnd(IntPtr hWnd)
        {
            this.hWnd = hWnd;
        }

        public WinWnd(WinWnd other)
        {
            CopyFrom(other);
        }

        public void ListenCalls()
        {
            InjectClient.InjectEvent += new EventHandler<CallEventArgs>(OnCall);
        }

        protected virtual void OnCall(object sender, CallEventArgs e)
        {
            if (e.Function.StartsWith("ExtTextOut"))
            {
                MyLogger.WriteLine("!ExtTextOut(DC={0},hWND={1}, X={2},Y={3},\'{4}\'", e.Args[0], e.Args[1], e.Args[2], e.Args[3], e.Args[6]);
                OnTextOut(
                    (Int32)e.Args[2],
                    (Int32)e.Args[3],
                    e.Args[6] as string,
                    e);
            }
        }


        public override string ToString()
        {
            return string.Format("hWnd={0} Text={1} Class={2} Visible={3} Rect={4}", Handle, Text, ClassName, Visible, Rect);
        }
    }

    public class WinWndMap : IEnumerable<WinWnd>
    {
        private Dictionary<IntPtr, WinWnd> _map = new Dictionary<IntPtr, WinWnd>();

        public static WinWndMap Instance;

        public WinWndMap()
        {
            Instance = this;
        }
        public WinWnd this[IntPtr hWnd]
        {
            get
            {
                WinWnd wnd = null;
                _map.TryGetValue(hWnd, out wnd);
                return wnd;
            }
        }

        public WinWnd FromDC(IntPtr dc)
        {
            IntPtr hwnd = Api.WindowFromDC(dc);
            if (hwnd == IntPtr.Zero)
                return null;
            return this[hwnd];
        }



        public T FromType<T>(WinWnd parent) where T : WinWnd
        {
            foreach (KeyValuePair<IntPtr, WinWnd> kv in _map)
            {
                if (kv.Value is T)
                    if (null == parent || kv.Value.Parent == parent)
                        return kv.Value as T;
            }
            return null;
        }

        public void Add(WinWnd wnd)
        {
            _map[wnd.Handle] = wnd;
        }

        #region IEnumerable<WinWnd> Members

        public IEnumerator<WinWnd> GetEnumerator()
        {
            return _map.Values.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _map.GetEnumerator();
        }

        #endregion
    }
}